
#include <iostream>
#include "HTTPResponse.h"

/** 存在大量的字符串拼接操作
 * 考虑使用std::stringstream和std::string_view优化
 */

const std::string_view HTTPResponse::CRLF("\r\n");

HTTPResponse::HTTPResponse() : code(), path(), root_path(get_current_dir_name()), file(nullptr), file_state({}),
                               now(), ss("") {
    ss.clear();
}

HTTPResponse::~HTTPResponse() {
    if (file) {
        munmap(file, file_state.st_size);
        file = nullptr;
    }
}

// 这里不考虑如果HTTP请求存在查询字符串的情况
void HTTPResponse::generate_response(const HTTPRequest &request, Buffer &buffer) {
    if (request.get_http_code() == BAD_REQUEST) {
        error_page(NOT_FOUND, buffer);
        return;
    }
    buffer.reserve(152);

    // 不考虑文件后缀没有带html
    std::string filename = std::move(this->root_path + "/resources" + request.get_url());
    if (stat(filename.c_str(), &this->file_state) < 0 || S_ISDIR(file_state.st_mode)) {  // 如果文件不存在或者是个目录
        error_page(NOT_FOUND, buffer);
        return;
    } else if (!(this->file_state.st_mode & S_IROTH)) {  // 是否有读权限
        error_page(FORBIDDEN, buffer);
        return;
    } else {
        this->code = OK;
    }
    // 写状态行
    try {
        ss << "HTTP/1.1 " << this->code << " " << this->STATUS_CODE.at(this->code) << CRLF;
        buffer.append(ss.str());
    } catch (std::out_of_range &e) {
        error_page(INTERNAL_SERVER_ERROR, buffer);
    }

    // 写响应头
    ss.clear();  // 可以用内联函数封装一下吗？
    ss.str("");
    ss << "Connection: ";
    if (request.is_keep_alive()) {
        ss << "keep-alive" << CRLF;
        buffer.append(ss.str());
    } else {
        ss << "close" << CRLF;
        buffer.append(ss.str());
    }
    ss.clear();
    ss.str("");
    std::string_view file_type;
    std::string::size_type pos = filename.find_last_of('.');
    if (pos == std::string::npos) {
        file_type = ".txt";
    } else {
        file_type = filename.substr(pos);
    }
    ss << "Content-type: " << SUFFIX_TYPE.at(file_type) << "; charset=utf-8" << CRLF;
    buffer.append(ss.str());

    // 写响应内容
    int file_fd = open(filename.c_str(), O_RDONLY);
    if (file_fd == -1) {
        // 定位500错误页面
        error_page(INTERNAL_SERVER_ERROR, buffer);
        return;
    }

    this->file = (char *) mmap(nullptr, this->file_state.st_size, PROT_READ, MAP_PRIVATE, file_fd, 0);
    close(file_fd);

    ss.clear();
    ss.str("");
    this->now = std::time(nullptr);
    const char *fmt = "%a, %d %b %Y %H:%M:%S GMT";
    char str[30];
    std::strftime(str, sizeof(str), fmt, gmtime(&now));
    ss << "Date: " << str << CRLF;
    buffer.append(ss.str());

    ss.clear();
    ss.str("");
    ss << "Content-length: " << file_state.st_size << CRLF << CRLF;
    buffer.append(ss.str());
}

void HTTPResponse::error_page(HTTP_CODE error_code, Buffer &buffer) {
    struct stat error_file_state{};
    std::string filename = std::move(this->root_path + "/resources/" + std::to_string(error_code) + ".html");
    stat(filename.c_str(), &error_file_state);
    std::cout << "error filename: " << filename << std::endl;

    buffer.clear();
    ss.clear();
    ss.str("");
    ss << "HTTP/1.1 " << error_code << " " << this->STATUS_CODE.at(error_code) << CRLF;
    buffer.append(ss.str());

    ss.clear();
    ss.str("");
    ss << "Connection: keep-alive" << CRLF;
    buffer.append(ss.str());

    ss.clear();
    ss.str("");
    ss << "Content-type: " << SUFFIX_TYPE.at(".html") << "; charset=utf-8" << CRLF;
    buffer.append(ss.str());

    // 写入对应的错误页面
//    int file_fd = open(filename.c_str(), O_RDONLY);
//    this->file = (char *) mmap(nullptr, error_file_state.st_size, PROT_READ, MAP_PRIVATE, file_fd, 0);
//    close(file_fd);

    ss.clear();
    ss.str("");
    this->now = std::time(nullptr);
    const char *fmt = "%a, %d %b %Y %H:%M:%S GMT";
    char str[30];
    std::strftime(str, sizeof(str), fmt, gmtime(&now));
    ss << "Date: " << str << CRLF;
    buffer.append(ss.str());

    ss.clear();
    ss.str("");
    ss << "Content-length: " << error_file_state.st_size << CRLF << CRLF;
    buffer.append(ss.str());
}

char *HTTPResponse::get_file() const {
    return file;
}

size_t HTTPResponse::get_file_length() const {
    return file_state.st_size;
}

